0x01 题设
这是来自慕尼黑工业大学的一场CTF,题干没有什么卵用,介绍了一种很奇葩的运动Haggis hurling,最后给出了一份源码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#!/usr/bin/env python3
import os, binascii, struct
from Crypto.Cipher import AES
pad = lambda m: m + bytes([16 - len(m) % 16] * (16 - len(m) % 16))
def haggis(m):
crypt0r = AES.new(bytes(0x10), AES.MODE_CBC, bytes(0x10))
return crypt0r.encrypt(len(m).to_bytes(0x10, 'big') + pad(m))[-0x10:]
target = os.urandom(0x10)
print(binascii.hexlify(target).decode())
msg = binascii.unhexlify(input())
if msg.startswith(b'I solemnly swear that I am up to no good.\0') \
and haggis(msg) == target:
print(open('flag.txt', 'r').read().strip())
0x02 write-up
从题目中可以看出在服务器端进行着两个判断:
一是对方发出的字符串S是否包含特定的前缀pre:I solemnly swear that I am up to no good.\0;
二是S经过一种特殊封包后的AES加密密文的后16位是否与服务器产生的16位随机字符random一致。
前者很容易就可以解决,后者需要对AES的加密方式有一定的了解才能完成任务。
首先统计一下pre的字符串长度为42bytes,我们假定要在该pre后填充X个字符,那么填充过后信息 M的长度为$$len(M)=len(pre+X) =42+len(X)$$之后进入一个封包的过程:
前0x10字节采用大端序存储M的长度,之后为原始信息M,长度为len(M),最后为N个填充字符c,具体计算公式为$$N = 16-len(M)(mod\;16)$$字符c的内容为$chr(16-len(M)(mod\; 16))$。
最终得到待加密原始明文M',长度为$$len(M’) = 16+len(M)+N$$将上边的内容一一带入可得:$$len(M’) = 16+42+len(X)+16-len(M)(mod\;16)$$
由于源码中没有对AES的填充规则进行设置(上述封包过程中所采用的是PKCS#7),所以我这里猜测M'的长度必须要为key即(bytes(0x10))整数倍,所以X可以取值为$6+16k,(k =0,1,2,…)$此时填充字符也就确定下来,为16位0x10,因此想要使它的加密结果为系统产生的16位随机字符random还需要借助前一个分组的帮助。
首先将确定X的长度,由于需要我们构造出一个新的分组,所以简便起见,我们选取k=1时的值即6+16,之后将前6字符我们可以随机填充,这样得到了前四个分组16+42+6=64进行AES加密(key为bytes(0x10)iv为bytes(0x10)),取出第四个分组加密后的16bytes密文称为enc,同时将random进行解密,key与iv都为bytes(0x10),得到中间值t_random之后与bytes(0x10)即填充字符串进行异或得到enc_last,将其像random一样再一次进行AES解密,得到t_enc_last,那么我们构造的16bytes明文字符串last即为
$$last = t\_enc\_last \oplus enc$$
这样一来,整个字符串M已被我们构造出来。nc连接到服务端得到随机字符串random再将构造好的M转成16进制流输入,便可get到flag。
new:新鲜出炉的官方题解